const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromsie {
  constructor(fn) {
    // 执行器，立刻执行
    try {
      fn(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  value = undefined
  reason = undefined
  status = PENDING
  successCbs = []
  failCbs = []

  resolve = (val) => {
    if (this.status !== PENDING) return
    this.value = val
    this.status = FULFILLED
    this.successCbs.length && this.successCbs.forEach(cb => cb())
  }

  reject = (reason) => {
    if (this.status !== PENDING) return
    this.reason = reason
    this.status = REJECTED
    this.failCbs.length && this.failCbs.forEach(cb => cb())
  }

  then = (successCb, failCb) => {
    successCb = typeof successCb === 'function' ? successCb : value => value
    // failCb = typeof failCb === 'function' ? failCb : reason => reason
  
    const p = new MyPromsie((resolve, reject) => {
      if (this.status === FULFILLED) {
          setTimeout(() => {
            try {
              const v = successCb(this.value)
              resolvePromise(v, resolve, reject, p)
            } catch (error) {
              reject(error)
            }
          }, 0);
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            if (typeof failCb !== 'function') reject(this.reason)
            const v = failCb(this.reason)
            resolvePromise(v, resolve, reject, p)
          } catch (error) {
            reject(error)
          }
        }, 0);
      } else {
        this.successCbs.push(() => {
          setTimeout(() => {
            try {
              const v = successCb(this.value)
              resolvePromise(v, resolve, reject, p)
            } catch (error) {
              reject(error)
            }
          }, 0);
        })
        this.failCbs.push(() => {
          setTimeout(() => {
            try {
              if (typeof failCb !== 'function') reject(this.reason)
              const v = failCb(this.reason)
              resolvePromise(v, resolve, reject, p)
            } catch (error) {
              reject(error)
            }
          }, 0);
        })
      }
    })

    return p
  }

  catch(cb) {
    return this.then(undefined, cb)
  }

  finally(cb) {
    return this.then(val => {
      return MyPromsie.resolve(cb()).then(() => val)
    }, reason => {
      return MyPromsie.resolve(cb()).then(() => {
        throw reason
      })
    })
  }

  static all(array) {
    // if (!array || !array.length) return

    return new MyPromsie((resolve, reject) => {
      let successFlag = 0
      let result = []
      for (let i = 0; i < array.length; i++) {
        const p = array[i]
        if (p instanceof MyPromsie) {
          p.then((val) => {
            result[i] = val
            successFlag++
            
            if (successFlag === array.length) {
              resolve(result)
            }
          }, reason => {
            reject(reason)
          })
        } else {
          result[i] = p
          successFlag++
        }
      }
    })
  }

  static resolve(val) {
    if (val instanceof MyPromsie) {
      return val
    } else {
      return new MyPromsie((resolve, reject) => {
        resolve(val)
      })
    }
  }
}

function resolvePromise(val, resolve, reject, p) {
  if (val === p) {
    return reject(new TypeError('cannot return itself!'))
  }
  if (val instanceof MyPromsie) {
    val.then(val => {
      resolve(val)
    }, reason => {
      reject(reason)
    })
  } else {
    resolve(val)
  }
}

module.exports = MyPromsie